package com.platform.jpa.dao.impl;

import com.platform.jpa.dao.IBaseDao;
import com.platform.jpa.dao.other.Condition;
import com.platform.jpa.dao.other.Operation;
import com.platform.jpa.utils.ArrayUtil;
import com.platform.jpa.utils.DateUtil;
import com.platform.jpa.utils.ReflectUtil;
import com.platform.jpa.utils.StringUtil;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@NoRepositoryBean
public class BaseDaoImpl<T, ID> extends SimpleJpaRepository<T, ID> implements IBaseDao<T, ID> {
    private EntityManager em;
    protected Class<T> clsT;

    public BaseDaoImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.em = entityManager;
        this.clsT = domainClass;
    }

    public BaseDaoImpl(JpaEntityInformation<T, Serializable> information, EntityManager entityManager) {
        super(information, entityManager);
        this.em = entityManager;
    }

    //-------------------增----------------------//
    @Modifying
    @Override
    public <S extends T> int saveAllV2(Iterable<S> entities) {
        if (!entities.iterator().hasNext())
            return 0;
        String tableName = getTableName();
        Field[] fields = FieldUtils.getAllFields(getTClass());
        String tmp;
        List<String> fieldsNames = new ArrayList<>();
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (column != null && !StringUtil.isEmpty(column.name()))
                tmp = column.name();
            else
                tmp = field.getName();
            fieldsNames.add(tmp);
        }
        String fieldNames = String.join(",", fieldsNames);

        List<String> allValues = new ArrayList<>();
        List<String> values = new ArrayList<>();

        entities.forEach(t -> {
            values.clear();
            for (Field field : fields) {
                field.setAccessible(true);
                Object value = null;
                try {
                    value = field.get(t);
                    if (field.getName().equals("createTime") || field.getName().equals("createTime")) {
                        values.add("'" + DateUtil.toString(new Date(), "yyyy-MM-dd HH:mm:ss") + "'");
                    } else if (value == null) {
                        values.add("NULL");
                    } else if (field.getType().isEnum())
                        values.add(((Enum) value).ordinal() + "");
                    else if (field.getType() == Date.class) {
                        values.add("'" + DateUtil.toString((Date) value, "yyyy-MM-dd HH:mm:ss") + "'");
                    } else if (field.getType() == String.class) {
                        if ("null".equals(value.toString().toLowerCase()))
                            values.add("NULL");
                        else
                            values.add("'" + value.toString() + "'");
                    } else if (field.getType() == Boolean.class) {
                        values.add((boolean) value ? "1" : "0");
                    } else {
                        values.add(value.toString());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                field.setAccessible(false);
            }
            allValues.add(String.format("(%s)", String.join(",", values)));
        });
        String sql = String.format("INSERT INTO %s (%s) VALUES %s", tableName, fieldNames, String.join(",", allValues));
        Query query = em.createNativeQuery(sql);
        int size = query.executeUpdate();
        // String ids = findFirstBySql("SELECT LAST_INSERT_ID() as ids", String.class);
        // entities.forEach(t -> ReflectUtil.setFieldValue(t, "", ));
        return size;
    }

    @Modifying
    @Override
    public boolean saveOrUpdate(Set<ID> existIds, List<T> entities) {
        // 处理新增
        List<T> adds = entities.stream()
                .filter(t -> "0".equals(t.toString()) || t.toString().startsWith("-"))
                .collect(Collectors.toList());
        // 处理更新
        List<T> updates = entities.stream()
                .filter(t -> !"0".equals(t.toString()) && !t.toString().startsWith("-"))
                .collect(Collectors.toList());
        // 处理删除
        Set<String> setIds = existIds.stream().map(t -> t.toString()).collect(Collectors.toSet());
        Iterable<ID> dels = entities.stream()
                .filter(t -> !setIds.contains(t.toString()))
                .map(t -> getIDClass().cast(t.toString()))
                .collect(Collectors.toList());

        if (adds.size() > 0) {
            int tmp = this.saveAllV2(adds);
            if (tmp <= 0) return false;
        }
        if (updates.size() > 0) {
            List tmps = updateList(updates);
            if (tmps == null || tmps.size() <= 0) return false;
        }
        if (dels.iterator().hasNext()) {
            deleteByIds(dels);
        }
        return true;
    }

    @Override
    public T saveOrUpdate(T entity) {
        Object obj = ReflectUtil.getFieldValue(entity, "id");
        if (obj == null)
            throw new RuntimeException("Id filed not found!");
        T t;
        if (obj.toString() == "0") {
            t = save(entity);
        } else {
            t = update(entity);
        }
        if (t == null) {
            throw new RuntimeException("saveOrUpdate error!");
        }
        return t;
    }

    //-------------------删----------------------//
    @Modifying
    @Override
    public int delete(Iterable<Condition> fields) {
        String formatSql = "delete from %s where %s";
        String sql = String.format(formatSql, getTableName(), getWhereSql(fields));

        Query query = em.createNativeQuery(sql);
        return query.executeUpdate();
    }

    @Modifying
    @Override
    public int delete(String name, Operation op, Object value, boolean not) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition(name, op, value, not));
        return delete(fields);
    }

    @Modifying
    @Override
    public void deleteByIdWithFlag(ID id) {
        updateById("del", true, id);
    }

    @Modifying
    @Override
    public void deleteByIds(Iterable<ID> ids) {
        Iterator<ID> it = ids.iterator();
        while (it.hasNext()) {
            deleteById(it.next());
        }
    }

    @Modifying
    @Override
    public int deleteByIdsV2(List<ID> ids) {
        String formatSql = "DELETE FROM %s WHERE id IN (%s)";
        return deleteBySql(String.format(formatSql, getTableName(), ArrayUtil.listToString(ids, ",")));
    }

    @Modifying
    @Override
    public int deleteBySql(String sql) {
        Query query = em.createNativeQuery(sql);
        return query.executeUpdate();
    }

    //-------------------改----------------------//
    @Modifying
    @Override
    public T update(T entity) {
        return em.merge(entity);
    }

    @Modifying
    @Override
    public Iterable<T> update(Iterable<T> entities) {
        T tmp;
        List<T> ts = new ArrayList<>();
        for (Iterator<T> it = entities.iterator(); it.hasNext(); ) {
            tmp = it.next();
            ts.add(em.merge(tmp));
        }
        return ts;
    }

    @Override
    public List<T> updateList(List<T> entities) {
        T tmp;
        List<T> ts = new ArrayList<>();
        for (Iterator<T> it = entities.iterator(); it.hasNext(); ) {
            tmp = it.next();
            ts.add(em.merge(tmp));
        }
        return ts;
    }

    @Modifying
    @Override
    public int update(Map<String, Object> param, Iterable<Condition> fields) {
        String formatSql = "update %s set %s where %s";
        String sql = String.format(formatSql, getTableName(), getUpdateSql(param), getWhereSql(fields));

        return updateBySql(sql);
    }

    @Modifying
    @Override
    public int update(String name, Object value, Iterable<Condition> fields) {
        Map<String, Object> param = new HashMap<>();
        param.put(name, value);
        return update(param, fields);
    }

    @Modifying
    @Override
    public int updateById(Map<String, Object> param, ID id) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition("Id", Operation.EQ, id));
        return update(param, fields);
    }

    @Modifying
    @Override
    public int updateById(String name, Object value, ID id) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition("Id", Operation.EQ, id));

        Map<String, Object> values = new HashMap<>();
        values.put(name, value);

        return update(values, fields);
    }

    @Modifying
    @Override
    public int update(String name, Object value, ID id) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition("Id", Operation.EQ, id));

        Map<String, Object> values = new HashMap<>();
        values.put(name, value);

        return update(values, fields);
    }

    @Modifying
    @Override
    public int updateBySql(String sql) {
        Query query = em.createNativeQuery(sql);
        return query.executeUpdate();
    }

    @Modifying
    @Override
    public int updateLockById(Map<String, Object> param, ID id, int lockIndex) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition("id", Operation.EQ, id));
        fields.add(new Condition("lockIndex", Operation.EQ, lockIndex));

        param.put("lockIndex", lockIndex + 1);
        return update(param, fields);
    }

    @Modifying
    @Override
    public int updateLockById(String fieldName, Object value, ID id, int lockIndex) {
        Map<String, Object> param = new HashMap<>();
        param.put(fieldName, value);
        return updateLockById(param, id, lockIndex);
    }

    //-------------------查----------------------//
    // region 查
    @Override
    public T findTById(ID id) {
        Optional<T> t = findById(id);
        return t.isPresent() ? t.get() : null;
    }

    @Override
    public T findFirst(String fieldName, Operation op, Object value, boolean not) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition(fieldName, op, value, not));
        Map<String, com.platform.jpa.dao.other.Sort> sorts = null;
        return findFirst(fields, sorts);
    }

    @Override
    public T findFirst(String fieldName, Operation op, Object value) {
        return findFirst(fieldName, op, value, false);
    }

    @Override
    public List<T> findPage(int page, int row, Iterable<Condition> fields, Map<String, com.platform.jpa.dao.other.Sort> sorts) {
        if (page < 0) return find(fields, sorts);
        Pageable pageable = PageRequest.of(--page, row, getSortClause(sorts));
        Page<T> pt = findAll(getWhereClause(fields), pageable);
        List<T> list = pt.getContent();
        if (list == null) return new ArrayList<>();
        return list;
    }

    /*
	@Override
    public List<T> findPage(int page, int row, Iterable<Condition> fields, Map<String, Boolean> sorts) {
        return findPage(page, row, fields, mapToSort(sorts));
    }
    */

    private Map<String, com.platform.jpa.dao.other.Sort> mapToSort(Map<String, Boolean> sorts) {
        Map<String, com.platform.jpa.dao.other.Sort> tmp = null;
        if (sorts != null) {
            tmp = new HashMap<>();
            for (String key : sorts.keySet()) {
                if (sorts.get(key)) {
                    tmp.put(key, com.platform.jpa.dao.other.Sort.DESC);
                } else {
                    tmp.put(key, com.platform.jpa.dao.other.Sort.ASC);
                }
            }
        }
        return tmp;
    }

    @Override
    public List<T> find(Iterable<Condition> fields, Map<String, com.platform.jpa.dao.other.Sort> sorts) {
        return findAll(getWhereClause(fields), getSortClause(sorts));
    }

    /*
	@Override
    public List<T> find(Iterable<Condition> fields, Map<String, Boolean> sorts) {
        return findAll(getWhereClause(fields), getSortClause(sorts));
    }
    */

    @Override
    public List<T> find(Iterable<Condition> fields) {
        return find(fields, null);
    }

    @Override
    public List<T> find(String fieldName, Operation op, Object value, boolean not) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition(fieldName, op, value, not));
        return find(fields, null);
    }

    @Override
    public List<T> find(String fieldName, Operation op, Object value) {
        return find(fieldName, op, value, false);
    }

    @Override
    public List<T> findPage(int page, int row, String fieldName, Operation op, Object value, boolean not) {
        List<Condition> fields = Arrays.asList(new Condition(fieldName, op, value, not));
        return findPage(page, row, fields, null);
    }

    @Override
    public T findFirst(Iterable<Condition> fields, Map<String, com.platform.jpa.dao.other.Sort> sorts) {
        List<T> ts = (List<T>) findPage(1, 1, fields, sorts);
        return null == ts || 0 >= ts.size() ? null : ts.get(0);
    }

    /*
	@Override
    public T findFirst(Iterable<Condition> fields, Map<String, Boolean> sorts) {
        List<T> ts = (List<T>) findPage(1, 1, fields, sorts);
        return null == ts || 0 >= ts.size() ? null : ts.get(0);
    }
    */

    @Override
    public T findFirst(Iterable<Condition> fields) {
        return findFirst(fields, (Map<String, com.platform.jpa.dao.other.Sort>) null);
    }

    @Override
    public Iterable<T> findBySql(String sql) {
        Query query = em.createNativeQuery(sql, getTClass());
        Iterable<T> ts = query.getResultList();
        return ts;
    }

    @Override
    public List<Map<String, Object>> findMapBySql(String sql) {
        Query query = em.createNativeQuery(sql);
        List list = query.getResultList();
        if (null == list || list.isEmpty())
            return null;

        List<Object[]> result = (List<Object[]>) list;
        if (result == null || result.isEmpty())
            return null;
        String[] fields = getResultFields(sql);
        if (fields.length == 1) {
            Object tmp = result.get(0);
            Map<String, Object> map = new HashMap<>();
            map.put(fields[0], tmp);
            return Arrays.asList(map);
        }

        return result.stream().map(o -> {
            Map<String, Object> map = new HashMap<>();
            for (int i = 0; i < fields.length; i++) {
                map.put(fields[i], o[i]);
            }
            return map;
        }).collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> findFisrtMapBySql(String sql) {
        Query query = em.createNativeQuery(sql);
        query.setMaxResults(1);
        List list = query.getResultList();
        if (null == list || list.isEmpty())
            return null;

        List<Object[]> result = (List<Object[]>) list;
        if (result == null || result.isEmpty())
            return null;
        String[] fields = getResultFields(sql);

        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < fields.length; i++) {
            map.put(fields[i], result.get(0)[i]);
        }
        Timestamp r;
        return map;
    }

    @Override
    public List<Map<String, Object>> findPageMapBySql(int page, int row, String sql) {
        return findMapBySql(handlePage(sql, page, row));
    }

    @Override
    public Iterable<T> findPageBySql(int page, int row, String sql) {
        return findBySql(handlePage(sql, page, row));
    }

    @Override
    public T findFirstBySql(String sql) {
        Query query = em.createNativeQuery(sql, getTClass());
        query.setMaxResults(1);
        try {
            return (T) query.getSingleResult();
        } catch (NoResultException nre) {
            return null;
        }
    }

    @Override
    public <M> Iterable<M> findPageBySql(int page, int row, String sql, Class<M> clsM) {
        return findBySql(handlePage(sql, page, row), clsM);
    }

    protected String handlePage(String sql, int page, int row) {
        String formatSql = "%s LIMIT %d, %d";
        int start = (0 == page ? 0 : (page - 1)) * row;
        return String.format(formatSql, sql, start, row);
    }

    @Override
    public <M> Iterable<M> findBySql(String sql, Class<M> clsM) {
        Query query = em.createNativeQuery(sql);
        List list = query.getResultList();
        if (null == list || list.isEmpty())
            return null;

        return handleListResult(list, clsM, sql);
    }


    @Override
    public <M> M findFirstBySql(String sql, Class<M> clsM) {
        Query query = em.createNativeQuery(sql);
        query.setMaxResults(1);
        List list = query.getResultList();
        if (list.isEmpty())
            return null;
        Object value = list.get(0);
        if (null == value)
            return null;
        if (clsM == String.class
                || value instanceof Integer
                || value instanceof Boolean
                || value instanceof Date
                || value instanceof Timestamp)
            return (M) value;
        else if (value instanceof BigInteger)
            return (M) new Long((((BigInteger) value).longValue()));
        else if (value instanceof BigDecimal) {
            if (clsM == Long.class)
                return (M) new Long(((BigDecimal) value).longValue());
            else if (clsM == Double.class)
                return (M) new Double((((BigDecimal) value).doubleValue()));
            else if (clsM == Float.class)
                return (M) new Float((((BigDecimal) value).floatValue()));
            else if (clsM == Integer.class)
                return (M) new Integer((((BigDecimal) value).intValue()));
        }
        return map(clsM, (Object[]) value, getResultFields(sql, clsM));
    }

    @Override
    public long count(Iterable<Condition> fields) {
        if (null == fields)
            return count();
        return count(getWhereClause(fields));
    }

    @Override
    public long countBySql(String sql) {
        Query query = em.createQuery(sql);
        query.setMaxResults(1);
        List list = query.getResultList();
        if (list.isEmpty())
            return 0;
        Object value = list.get(0);
        return (long) value;
    }

    @Override
    public long countBySqlV2(String sql) {
        Long cnt = findFirstBySql(sql, Long.class);
        if (null == cnt)
            return 0;
        return cnt;
    }

    @Override
    public long count(String name, Operation op, Object value, boolean not) {
        List<Condition> fields = new ArrayList<>();
        fields.add(new Condition(name, op, value, not));

        return count(fields);
    }

    // endregion
    //-------------------其他----------------------//
    public void flush() {
        em.flush();
    }

    //-------------------基础----------------------//

    protected org.springframework.data.domain.Sort getSortClause(Map<String, com.platform.jpa.dao.other.Sort> sorts) {
        if (null == sorts)
            return org.springframework.data.domain.Sort.unsorted();
        List<org.springframework.data.domain.Sort.Order> orders = new ArrayList<>();
        Iterator<String> it = sorts.keySet().iterator();
        String name;
        while (it.hasNext()) {
            name = it.next();
            if (sorts.get(name) == com.platform.jpa.dao.other.Sort.ASC) {
                orders.add(new org.springframework.data.domain.Sort.Order(org.springframework.data.domain.Sort.Direction.ASC, name).ignoreCase());
            } else {
                orders.add(new org.springframework.data.domain.Sort.Order(org.springframework.data.domain.Sort.Direction.DESC, name).ignoreCase());
            }

        }
        return Sort.by(orders);
    }

    protected Specification<T> getWhereClause(Iterable<Condition> fields) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                if (null == fields)
                    return null;
                List<Predicate> predicate = new ArrayList<>();
                Iterator<Condition> it = fields.iterator();
                Condition c;
                boolean isOr = false;
                Predicate tmp;
                int size;
                while (it.hasNext()) {
                    c = it.next();
                    size = predicate.size() - 1;
                    switch (c.getOp()) {
                        case EQ:
                            if (c.getValue() instanceof Date)
                                tmp = cb.equal(root.get(c.getName()), c.getValue());
                            else
                                tmp = cb.equal(root.get(c.getName()), c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else if (c.isNot()) {
                                predicate.add(tmp.not());
                            } else
                                predicate.add(tmp);
                            break;
                        case GT:
                            if (c.getValue() instanceof Date)
                                tmp = cb.greaterThan(root.get(c.getName()),
                                        (Date) c.getValue());
                            else
                                tmp = cb.gt(root.get(c.getName()),
                                        (Number) c.getValue());

                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else if (c.isNot())
                                predicate.add(tmp.not());
                            else
                                predicate.add(tmp);
                            break;
                        case GE:
                            if (c.getValue() instanceof Date) {
                                tmp = cb.greaterThanOrEqualTo(root.get(c.getName()),
                                        (Date) c.getValue());
                            } else
                                tmp = cb.ge(root.get(c.getName()),
                                        (Number) c.getValue());

                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else if (c.isNot())
                                predicate.add(tmp.not());
                            else
                                predicate.add(tmp);
                            break;
                        case LT:
                            if (c.getValue() instanceof Date)
                                tmp = cb.lessThan(root.get(c.getName()),
                                        (Date) c.getValue());
                            else
                                tmp = cb.lt(root.get(c.getName()),
                                        (Number) c.getValue());

                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else if (c.isNot())
                                predicate.add(tmp.not());
                            else
                                predicate.add(tmp);
                            break;
                        case LE:
                            if (c.getValue() instanceof Date) {
                                tmp = cb.lessThanOrEqualTo(root.get(c.getName()),
                                        (Date) c.getValue());
                            } else
                                tmp = cb.le(root.get(c.getName()),
                                        (Number) c.getValue());

                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else if (c.isNot())
                                predicate.add(tmp.not());
                            else
                                predicate.add(tmp);
                            break;
                        case LIKE:
                            if (c.isNot())
                                tmp = cb.like(root.get(c.getName()), (String) c.getValue()).not();
                            else
                                tmp = cb.like(root.get(c.getName()), (String) c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case IN:
                            CriteriaBuilder.In<Object> in = cb.in(root.get(c.getName()));
                            ((List) c.getValue()).stream().forEach(v -> in.value(v));

                            if (c.isNot())
                                tmp = in.not(); // cb.in(root.get(c.getName()).in(c.getValue())).not();
                            else
                                tmp = in;
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case AND:
                            // predicate.get(predicate.size() - 1).anp redicate.add(cb.and(root.get(c.getName()), (Number) c.getValue()));
                            break;
                        case OR:
                            isOr = true;
                            break;
                        default:
                            break;
                    }
                }

                Predicate[] pre = new Predicate[predicate.size()];
                query.where(predicate.toArray(pre));

                return query.getRestriction();
            }
        };
    }

    protected Specification<T> getWhereClauseV2(Iterable<Condition> fields) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                if (null == fields)
                    return null;
                List<Predicate> predicate = new ArrayList<>();
                Iterator<Condition> it = fields.iterator();
                Condition c;
                boolean isOr = false;
                Predicate tmp;
                int size;
                while (it.hasNext()) {
                    c = it.next();
                    size = predicate.size() - 1;
                    switch (c.getOp()) {
                        case EQ:
                            if (c.isNot())
                                tmp = cb.equal(root.get(c.getName()), c.getValue()).not();
                            else
                                tmp = cb.equal(root.get(c.getName()), c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case GT:
                            if (c.isNot())
                                tmp = cb.gt(root.get(c.getName()),
                                        (Number) c.getValue()).not();
                            else
                                tmp = cb.gt(root.get(c.getName()), (Number) c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case GE:
                            if (c.isNot())
                                tmp = cb.ge(root.get(c.getName()), (Number) c.getValue()).not();
                            else
                                tmp = cb.ge(root.get(c.getName()), (Number) c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case LT:
                            if (c.isNot())
                                tmp = cb.lt(root.get(c.getName()), (Number) c.getValue()).not();
                            else
                                tmp = cb.lt(root.get(c.getName()), (Number) c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case LE:
                            if (c.isNot())
                                tmp = cb.le(root.get(c.getName()), (Number) c.getValue()).not();
                            else
                                tmp = cb.le(root.get(c.getName()), (Number) c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case LIKE:
                            if (c.isNot())
                                tmp = cb.like(root.get(c.getName()), (String) c.getValue()).not();
                            else
                                tmp = cb.like(root.get(c.getName()), (String) c.getValue());
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case IN:
                            if (c.isNot())
                                tmp = cb.in(root.get(c.getName()).in(c.getValue())).not();
                            else
                                tmp = cb.in(root.get(c.getName()).in(c.getValue()));
                            if (isOr) {
                                predicate.set(size, cb.or(predicate.get(size), tmp));
                                isOr = false;
                            } else
                                predicate.add(tmp);
                            break;
                        case AND:
                            // predicate.get(predicate.size() - 1).anp redicate.add(cb.and(root.get(c.getName()), (Number) c.getValue()));
                            break;
                        case OR:
                            isOr = true;
                            break;
                        default:
                            break;
                    }
                }

                Predicate[] pre = new Predicate[predicate.size()];
                query.where(predicate.toArray(pre));

                return query.getRestriction();
            }
        };
    }

    public Class<T> getTClass() {
        return clsT; // getClass(0);
    }

    protected Class<ID> getIDClass() {
        return getClass(1);
    }

    protected Class getClass(int index) {
        Type genType = getClass().getGenericSuperclass();
        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (index >= params.length || index < 0) {
            return Object.class;
        }

        if (!(params[index] instanceof Class)) {
            return Object.class;
        }

        return (Class) params[index];
    }

    protected <R> R getPropertyValue(T t, Class<R> clsR, String name) {
        try {
            Field field = t.getClass().getDeclaredField(name);
            field.setAccessible(true);
            Object obj = field.get(t);
            return clsR.cast(obj);
        } catch (Exception e) {
            return null;
        }
    }

    protected <S> S map(Class<S> type, Object[] tuple, String[] fields) {
        List<Class<?>> tupleTypes = new ArrayList<>();
        for (Object field : tuple) {
            if (null == field)
                continue;
            tupleTypes.add(field.getClass());
        }
        try {
            // Class<?> [] clss = tupleTypes.toArray(new Class<?>[tuple.length]);
            S s = type.newInstance();
            Field field;
            for (int i = 0; i < tuple.length; i++) {
                field = FieldUtils.getField(type, fields[i], true); //gettype.getField(fields[i]);
                if (field == null || tuple[i] == null) continue;
                field.setAccessible(true);
                field.set(s, getResultValue(field, tuple[i]));
                field.setAccessible(false);
            }
            // Constructor<S> ctor = type.getConstructor(clss);
            return s; // ctor.newInstance(tuple);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected Object getResultValue(Field field, Object value) {
        if (field.getType().isEnum())
            return field.getType().getEnumConstants()[value instanceof BigInteger ? ((BigInteger) value).intValue() : (int) value];
        if (value instanceof BigInteger) {
            if (null == value)
                return 0;
            else if (field.getType() == boolean.class) {
                return ((BigInteger) value).intValue() > 0 ? true : false;
            } else
                return ((BigInteger) value).intValue();
        } else if (value instanceof BigDecimal) {
            if (null == value)
                return 0;
            else if (field.getType() == Integer.class || field.getType().getName().equals("int")) {
                return ((BigDecimal) value).intValue();
            } else return ((BigDecimal) value).floatValue();
        } else if (value instanceof Integer && !field.getType().getName().equals("boolean")) {
            if (null == value)
                return 0;
            return ((Integer) value).intValue();
        } else if ((field.getType() == Boolean.class || field.getType().getName().equals("boolean")) && value instanceof Integer) {
            return (Integer) value > 0 ? true : false;
        } else
            return value;
    }

    protected Object getResultValue(Object value) {
        if (value instanceof BigInteger) {
            if (null == value)
                return 0;
            else
                return ((BigInteger) value).intValue();
        } else if (value instanceof BigDecimal) {
            if (null == value)
                return 0;
            else return ((BigDecimal) value).floatValue();
        } else
            return value;
    }

    protected <S> List<S> map(Class<S> type, List<Object[]> records, String[] fields) {
        List<S> result = new LinkedList<>();
        for (Object[] record : records) {
            result.add(map(type, record, fields));
        }
        return result;
    }

    public String[] getResultFields(String sql) {
        sql = sql.trim();
        int index1 = sql.indexOf("select ");
        index1 = (index1 > -1 ? index1 : sql.indexOf("select ".toUpperCase())) + "select ".length();
        int index2 = sql.indexOf(" from ");
        index2 = index2 > -1 ? index2 : sql.indexOf(" from ".toUpperCase());
        sql = sql.substring(index1, index2) + ",";

        String str;
        int quoaCnt = 0;
        List<String> listStr = new ArrayList<>();
        int start = 0;
        for (int i = 0; i < sql.length(); i++) {
            if (sql.charAt(i) == '(') {
                quoaCnt++;
            } else if (quoaCnt > 0 && sql.charAt(i) == ')') {
                quoaCnt--;
            } else if (quoaCnt <= 0 && sql.charAt(i) == ',') {
                str = sql.substring(start, i);
                start = i + 1;
                index1 = str.indexOf(" as ");
                index1 = index1 > -1 ? index1 : str.indexOf(" as ".toUpperCase());
                if (0 > index1) {
                    index2 = str.indexOf(".");
                    if (0 < index2) listStr.add(str.substring(index2 + 1).trim().replace("`", "").replace("'", ""));
                    else listStr.add(str.trim().replace("`", "").replace("'", ""));
                } else
                    listStr.add(str.substring(index1 + " as ".length()).trim().replace("`", "").replace("'", ""));
            }
        }

        return listStr.toArray(new String[listStr.size()]);
    }

    public <M> String[] getResultFields(Class<M> clz) {
        List<String> listStr = new ArrayList<>();
        // String tableName = getTableName(clz);
        Field[] fields = FieldUtils.getAllFields(clz);
        String tmp;
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (column != null && !StringUtil.isEmpty(column.name()))
                tmp = column.name();
            else
                tmp = field.getName();
            listStr.add(tmp);
        }

        return listStr.toArray(new String[listStr.size()]);
    }

    public <M> String[] getResultFields(String sql, Class<M> clz) {
        sql = sql.trim();
        int index1 = sql.indexOf("select ");
        index1 = (index1 > -1 ? index1 : sql.indexOf("select ".toUpperCase())) + "select ".length();
        int index2 = sql.indexOf(" from ");
        index2 = index2 > -1 ? index2 : sql.indexOf(" from ".toUpperCase());
        sql = sql.substring(index1, index2) + ",";

        String str;
        int quoaCnt = 0;
        List<String> listStr = new ArrayList<>();
        int start = 0;
        String field;
        for (int i = 0; i < sql.length(); i++) {
            if (sql.charAt(i) == '(') {
                quoaCnt++;
            } else if (quoaCnt > 0 && sql.charAt(i) == ')') {
                quoaCnt--;
            } else if (quoaCnt <= 0 && sql.charAt(i) == ',') {
                str = sql.substring(start, i);
                start = i + 1;
                index1 = str.indexOf(" as ");
                index1 = index1 > -1 ? index1 : str.indexOf(" as ".toUpperCase());
                if (0 > index1) {
                    index2 = str.indexOf(".");
                    if (0 < index2) {
                        field = str.substring(index2 + 1).trim().replace("`", "").replace("'", "");
                    } else {
                        field = str.trim().replace("`", "").replace("'", "");
                    }
                } else {
                    field = str.substring(index1 + " as ".length()).trim().replace("`", "").replace("'", "");
                }

                // 处理*问题
                if (field.contains("*")) {
                    // listStr.addAll(handleStarStr(sql, str.replace("*", "").replace(".", "").trim()));
                } else {
                    listStr.add(field);
                }
            }
        }

        return listStr.toArray(new String[listStr.size()]);
    }

    private List<String> handleStarStr(String sql, String strClz) {
        int index = sql.indexOf(" " + strClz + " ");
        try {
            Class.forName(strClz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getWhereSql(Iterable<Condition> fields) {
        String sql = "";
        Iterator<Condition> it = fields.iterator();
        Condition c;
        StringBuilder sb = new StringBuilder(" ");
        boolean lastLogic = true;
        while (it.hasNext()) {
            if (lastLogic)
                lastLogic = false;
            else
                sb.append(" and ");
            c = it.next();
            switch (c.getOp()) {
                case EQ:
                    sb.append(c.getName());
                    if (c.isNot())
                        sb.append(" != ");
                    else
                        sb.append(" = ");
                    sb.append(getValue(c.getValue()));
                    break;
                case GT:
                    sb.append(c.getName());
                    if (c.isNot())
                        sb.append(" <= ");
                    else
                        sb.append(" > ");
                    sb.append(getValue(c.getValue()));
                    break;
                case GE:
                    sb.append(c.getName());
                    if (c.isNot())
                        sb.append(" < ");
                    else
                        sb.append(" >= ");
                    sb.append(getValue(c.getValue()));
                    break;
                case LT:
                    sb.append(c.getName());
                    if (c.isNot())
                        sb.append(" >= ");
                    else
                        sb.append(" < ");
                    sb.append(getValue(c.getValue()));
                    break;
                case LE:
                    sb.append(c.getName());
                    if (c.isNot())
                        sb.append(" > ");
                    else
                        sb.append(" <= ");
                    sb.append(getValue(c.getValue()));
                    break;
                case LIKE:
                    sb.append(c.getName());
                    if (c.isNot())
                        sb.append(" not ");
                    else
                        sb.append(" like ");
                    sb.append(getValue(c.getValue()));
                    break;
                case IN:
                    sb.append(c.getName());
                    if (c.isNot())
                        sb.append(" not ");
                    else
                        sb.append(" in ");
                    sb.append(" (");
                    Iterator tit = ((Iterable) c.getValue()).iterator();
                    while (tit.hasNext()) {
                        sb.append(getValue(tit.next()));
                        if (tit.hasNext())
                            sb.append(",");
                    }
                    sb.append(") ");
                    break;
                case AND:
                    sb.append(" and ");
                    lastLogic = true;
                    break;
                case OR:
                    sb.append(" or ");
                    lastLogic = true;
                    break;
                default:
                    break;
            }
        }

        return sb.toString();
    }

    protected String getUpdateSql(Map<String, Object> param) {
        Iterator<String> itKey = param.keySet().iterator();
        StringBuilder sb = new StringBuilder(" ");
        String key;
        boolean update = false;
        while (itKey.hasNext()) {
            key = itKey.next();
            if (key.equals("updateTime"))
                update = true;
            sb.append(key).append(" = ").append(getValue(param.get(key)));
            if (itKey.hasNext())
                sb.append(",");
        }
        if (!update && getTField("updateTime") != null) {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sb.append(", updateTime = '").append(format.format(new Date())).append("'");
        }
        return sb.toString();
    }

    private Field getTField(String fieldName) {
        try {
            Field field = ReflectUtil.getField(getTClass(), fieldName);
            if (field == null)
                field = getTClass().getSuperclass().getDeclaredField(fieldName);
            return field;
            // return getTClass().getDeclaredField(fieldName);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    protected String getValue(Object obj) {
        if (obj == null)
            return "null";
        String str = obj.toString();
        if (0 <= str.indexOf('\'')) {
            str = str.replace("'", "''");
        }
        if (obj instanceof String) {
            if ("false".equals(str)) return "0";
            else if ("true".equals(str)) return "1";
            else return "'" + str + "'";
        } else if (obj instanceof Date) {
            return "'" + DateUtil.toDateTimeString((Date) obj) + "'";
        } else if (obj instanceof Enum) {
            return ((Enum) obj).ordinal() + "";
        }
        /*
        else if (obj instanceof Integer)
            Integer.parseInt(obj.toString());
        else if (obj instanceof Long)
            Long.parseLong(obj.toString());
        else if (obj instanceof Double)
            Double.parseDouble(obj.toString());
        else if (obj instanceof Float)
            return Float.parseFloat(obj.toString());
            */
        else return str;
    }

    protected Iterable handleListResult(List list, Class clsM, String sql) {
        if (null == list.get(0))
            return null;
        if (list.get(0) instanceof String
                || list.get(0) instanceof Integer
                || list.get(0) instanceof Boolean
                || list.get(0) instanceof Date)
            return list;
        if (list.get(0) instanceof BigInteger)
            return (Iterable) list.stream().map(p -> (BigInteger) p).collect(Collectors.toList());

        return map(clsM, (List<Object[]>) list, getResultFields(sql, clsM));
    }

    private String getTableName() {
        return getTableName(getTClass());
    }

    private <M> String getTableName(Class<M> cls) {
        Entity e = ReflectUtil.getAnnotation(cls, Entity.class);
        if (e == null || StringUtil.isEmpty(e.name()))
            return getTClass().getSimpleName();
        return e.name();
    }
}